﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using Tsanie.ShellExtension;
using Tsanie.ShellExtension.Util;

namespace Tsanie.FlvTag {
    public class FlvTagWriter {
        private FlvTagReader _tags;
        private double _rate;
        private double _time;
        private double _speed;
        private ushort _width, _heigth;
        private Action<string> _done;
        private long _seek;
        private long _length;
        private Action<long> _exec;

        public FlvTagReader Tags { set { _tags = value; } }
        public double BitRate { set { _rate = value; } }
        public double Time { set { _time = value; } }
        public double Speed { set { _speed = value; } }
        public ushort Width { set { _width = value; } }
        public ushort Height { set { _heigth = value; } }
        public Action<string> Done { set { _done = value; } }
        public long Seek { get { return _seek; } }
        public long Length { get { return _length; } }
        public Action<long> Exec { set { _exec = value; } }

        public FlvTagWriter() {
            _width = 512;
            _heigth = 384;
        }

        public Thread BlackExecute() {
            if (_tags == null) {
                _done.SafeRun("<Exception>ArgumentNull");
                return null;
            }
            double o_rate = _tags.Rate;
            if (o_rate < _rate) {
                // 不需要后黑
                _done.SafeRun("<Warning>NeednotExecute");
                return null;
            }
            _length = _tags.Length + 16;
            double duration;
            // 计算黑时间
            if (_time < 0) {
                duration = _length / 125.0 / _rate;    // *8/1000/rate
            } else {
                duration = _tags.Duration / 1000.0 + _time;
            }
            string offset = ((duration * 1000 - _tags.Duration) / 1000).ToString("0.000");
            _seek = 0;

            #region - Thread start -

            Thread thread = new Thread(delegate() {
                FileStream src = null, dest = null;
                try {
                    src = new FileStream(_tags.Filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    string path = _tags.Filename.ToPathWithoutExt() + ".black.flv";
                    dest = new FileStream(path, FileMode.Create);
                    int count = _tags.Tags.Count;
                    WriteHead(dest, _length, duration, -1, -1, -1, 1.0, 0, 0, count - 1, false);
                    byte[] buffer = new byte[_tags.MaxTagSize > 4 ? _tags.MaxTagSize : 4];
                    for (int i = 1; i < count; i++) {
                        FlvTag tag = _tags.Tags[i];
                        if (tag.Offset - 11 != src.Position) {
#if TRACE
                            Common.OutputDebugString("BlackExecute(void):: offset..[{0}] {1:X8} ==> {2:X8}",
                                i,
                                src.Position,
                                tag.Offset - 11);
#endif
                            src.Seek(tag.Offset - 11, SeekOrigin.Begin);
                        }
                        // Data
                        int c = src.Read(buffer, 0, tag.DataSize + 11);
                        dest.Write(buffer, 0, c);
                        // Prev tagsize
                        src.Read(buffer, 0, 4);
                        dest.Write(buffer, 0, 4);
                        // seek
                        _seek = src.Position;
                    }
                    src.Close();
                    buffer = new byte[] {
                        0x09, 0, 0, 0x01, // 视频帧 1 字节
                        0, 0, 0, 0,       // 04h, timestamp & ex
                        0, 0, 0,          // stream id
                        0x17,             // InnerFrame, H.264
                        0, 0, 0, 0x0c     // 此帧长度 12 字节
                    };
                    buffer.PutTime(0x04, (uint)(duration * 1000));
                    dest.Write(buffer, 0, buffer.Length);
                    dest.Flush();
                    dest.Close();
                    _done.SafeRun("<Info>ExecuteOK#\n" + offset);
                } catch (Exception e) {
                    if (src != null) src.Close();
                    if (dest != null) dest.Close();
                    _done.SafeRun(string.Format("<Exception>{0}\n{1}", e.Message, e.StackTrace));
                }
            });

            #endregion

            thread.Name = string.Format("thread_BlackExecute_rate:{0},time:{1},speed:{2}", _rate, _time, _speed);
            thread.Start();
            return thread;
        }
        public Thread FrontTsundere() {
            if (_tags == null) {
                _done.SafeRun("<Exception>ArgumentNull");
                return null;
            }
            double o_rate = _tags.Rate;
            if (o_rate < _rate) {
                // 不需要傲娇
                _done.SafeRun("<Warning>NeednotExecute");
                return null;
            }
            _length = _tags.Length;
            if (_rate < 0)  // 按时间傲娇
                _length += 27;
            uint offs;
            double duration;
            if (_time < 0) {
                // 计算傲娇时间
                duration = _length / 125.0 / _rate;
                offs = (uint)(duration * 1000 - _tags.Duration);
            } else {
                duration = _tags.Duration / 1000.0 + _time;
                offs = (uint)(_time * 1000);
            }
            string offset = (offs / 1000.0).ToString("0.000");
            _seek = 0;

            #region - Thread start -

            Thread thread = new Thread(delegate() {
                FileStream src = null, dest = null;
                try {
                    src = new FileStream(_tags.Filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    string path = _tags.Filename.ToPathWithoutExt() + ".front.flv";
                    dest = new FileStream(path, FileMode.Create);
                    int count = _tags.Tags.Count;
                    WriteHead(dest, _length, duration, 2.0, 2.0, -1, 1.0, 0 - offs, 0, count - 1, (_rate < 0));
                    // 傲娇才插入新帧
                    if (_rate < 0) {
                        byte[] buf = GetH263Frame(0, _width, _heigth);
                        dest.Write(buf, 0, buf.Length);
                    }

                    byte[] buffer = new byte[_tags.MaxTagSize > 4 ? _tags.MaxTagSize : 4];
                    bool flag = true;
                    for (int i = 1; i < count; i++) {
                        FlvTag tag = _tags.Tags[i];
                        if (tag.Offset - 11 != src.Position) {
#if TRACE
                            Common.OutputDebugString("BlackExecute(void):: offset..[{0}] {1:X8} ==> {2:X8}",
                                i,
                                src.Position,
                                tag.Offset - 11);
#endif
                            src.Seek(tag.Offset - 11, SeekOrigin.Begin);
                        }
                        src.Read(buffer, 0, 4);
                        dest.Write(buffer, 0, 4);
                        // 时间戳
                        uint t = tag.TimeStamp;
                        if (flag) {
                            VideoTag vtag = tag as VideoTag;
                            if (_time < 0) {
                                // 无傲娇进度条
                                if ((vtag != null) && (vtag.AVCPacketType == 1)) {
                                    t += offs;
                                    flag = false;
                                } else {
                                    t = 0;
                                }
                            } else {
                                // 傲娇
                                if (vtag != null) {
                                    if (vtag.FrameType == "keyframe") {
                                        flag = false;
                                    }
                                    t = 0;
                                } else {
                                    t += offs;
                                }
                            }
                        } else {
                            t += offs;
                        }
                        buffer.PutTime(0, t);
                        dest.Write(buffer, 0, 4);   // timestamp
                        // Data
                        src.Seek(4, SeekOrigin.Current);
                        src.Read(buffer, 0, 3); // streamid
                        dest.Write(buffer, 0, 3);
                        int c = src.Read(buffer, 0, tag.DataSize);
                        dest.Write(buffer, 0, c);
                        // Prev tagsize
                        src.Read(buffer, 0, 4);
                        dest.Write(buffer, 0, 4);
                        _seek = src.Position;
                    }
                    src.Close();
                    dest.Flush();
                    dest.Close();
                    _done.SafeRun("<Info>ExecuteOK#\n" + offset);
                } catch (Exception e) {
                    if (src != null) src.Close();
                    if (dest != null) dest.Close();
                    _done.SafeRun(string.Format("<Exception>{0}\n{1}", e.Message, e.StackTrace));
                }
            });

            #endregion

            thread.Name = string.Format("thread_FrontTsundere_rate:{0},time:{1},speed:{2}", _rate, _time, _speed);
            thread.Start();
            return thread;
        }
        public Thread RateExecute() {
            if (_tags == null) {
                _done.SafeRun("<Exception>ArgumentNull");
                return null;
            }
            double x;
            if (_speed < 0) {
                if (_time < 0) {
                    x = _tags.Rate / _rate; // 原比特率/目标比特率
                } else {
                    x = (_tags.Duration + _time * 1000) / _tags.Duration;   // 处理后时间/原时间
                }
            } else {
                x = _speed;
            }
            double duration = (_time > 0 ? (_tags.Duration / 1000.0 + _time) : (_tags.Duration * x / 1000.0));
            double framerate = 23.976;
            object o;
            if (_tags.MetaTag.TryGet("framerate", out o))
                framerate = (double)o / x;
            _length = _tags.Length;

            _seek = 0;

            #region - Thread start -

            Thread thread = new Thread(delegate() {
                FileStream src = null, dest = null;
                try {
                    src = new FileStream(_tags.Filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    string path = _tags.Filename.ToPathWithoutExt() + ".speed.flv";
                    dest = new FileStream(path, FileMode.Create);
                    int count = _tags.Tags.Count;
                    WriteHead(dest, _length, duration, -1, -1, framerate, x, 0, 0, count - 1, false);
                    byte[] buffer = new byte[_tags.MaxTagSize > 4 ? _tags.MaxTagSize : 4];
                    for (int i = 1; i < count; i++) {
                        FlvTag tag = _tags.Tags[i];
                        if (tag.Offset - 11 != src.Position) {
#if TRACE
                            Common.OutputDebugString("BlackExecute(void):: offset..[{0}] {1:X8} ==> {2:X8}",
                                i,
                                src.Position,
                                tag.Offset - 11);
#endif
                            src.Seek(tag.Offset - 11, SeekOrigin.Begin);
                        }

                        src.Read(buffer, 0, 4);
                        dest.Write(buffer, 0, 4);
                        // 时间戳
                        uint t = tag.TimeStamp;
                        t = (uint)(t * x);
                        buffer.PutTime(0, t);
                        dest.Write(buffer, 0, 4);   // timestamp
                        // Data
                        src.Seek(4, SeekOrigin.Current);
                        src.Read(buffer, 0, 3); // streamid
                        dest.Write(buffer, 0, 3);
                        int c = src.Read(buffer, 0, tag.DataSize);
                        dest.Write(buffer, 0, c);
                        // Prev tagsize
                        src.Read(buffer, 0, 4);
                        dest.Write(buffer, 0, 4);

                        _seek = src.Position;
                    }
                    src.Close();
                    dest.Flush();
                    dest.Close();
                    _done.SafeRun("<Info>ExecuteOK#\n" + x);
                } catch (Exception e) {
                    if (src != null) src.Close();
                    if (dest != null) dest.Close();
                    _done.SafeRun(string.Format("<Exception>{0}\n{1}", e.Message, e.StackTrace));
                }
            });

            #endregion

            thread.Name = string.Format("thread_RateExecute_rate:{0},time:{1},speed:{2}", _rate, _time, _speed);
            thread.Start();
            return thread;
        }

        private byte[] GetH263Frame(uint timestamp, ushort width, ushort height) {
            long b = (1 << 16) | width;
            b = (b << 16) | height;
            b <<= 7;
            byte[] buffer = new byte[]{
                0x09, 0, 0, 0x0c, // 视频帧 12 字节
                0, 0, 0, 0,       // timestamp & ex
                0, 0, 0,          // stream id
                0x22, 0, 0, 0x84, 0, // InnerFrame, H.263
                0, 0, 0, 0, 0, 0x12, 0x26, // 16~20:width x height
                0, 0, 0, 0x17     // 此帧长度 23 字节
            };
            buffer.PutTime(4, timestamp);
            for (int i = 0; i < 5; i++) {
                buffer[20 - i] = (byte)(b & 0xFF);
                b >>= 8;
            }
            return buffer;
        }
        private void WriteHead(
            Stream dest,
            long datasize,
            double duration,
            double vcodec,
            double acodec,
            double framerate,
            double x,
            uint offset_b,
            int f1,
            int f2,
            bool reserve
        ) {
            int framecount = f2 - f1 + 1;
            if (framecount <= 0)
                throw new Exception("帧不能为空！");

            double audiosize = 0;
            double videosize = 0;
            double audiocodec = acodec;
            double videocodec = vcodec;
            double lasttimestamp = 0;
            double lastkeyframetimestamp = 0;
            double lastkeyframelocation = 0;
            List<double> filepositions = new List<double>();
            List<double> times = new List<double>();

            long first_offset = 0;
            bool res = reserve;
            for (int i = f1; i <= f2; i++) {
                if ((first_offset == 0) && !(_tags.Tags[i] is ScriptTag)) {
                    first_offset = _tags.Tags[i].Offset;
                }
                AudioTag atag = _tags.Tags[i] as AudioTag;
                if (atag != null) {
                    if (audiocodec < 0)
                        audiocodec = atag.CodecId;
                    audiosize += atag.DataSize + 11;
                    continue;
                }
                VideoTag vtag = _tags.Tags[i] as VideoTag;
                if (vtag != null) {
                    if (videocodec < 0)
                        videocodec = vtag.CodecId;
                    videosize += vtag.DataSize + 11;
                    lasttimestamp = Math.Round((vtag.TimeStamp - offset_b) * x) / 1000.0;
                    if (vtag.FrameType == "keyframe") {
                        if (res) {
                            lasttimestamp = vtag.TimeStamp / 1000.0;
                            res = false;
                        }
                        lastkeyframetimestamp = lasttimestamp;
                        lastkeyframelocation = vtag.Offset - first_offset;
                        filepositions.Add(lastkeyframelocation);
                        times.Add(lastkeyframetimestamp);
                    }
                    continue;
                }
            }
            ScriptTag meta = _tags.MetaTag;

            byte[] bhead = new byte[] {
                0x46, 0x4c, 0x56, // FLV
                0x01,             // Version 1
                0x05,             // 0000 0101, 有音频有视频
                0, 0, 0, 0x09,    // Header size, 9
                0, 0, 0, 0,       // Previous Tag Size #0
            };
            int pos = 0;
            byte[] buffer = new byte[63356];
            buffer[pos++] = 0x12; // script

            #region - 开始写 -

            for (int i = 0; i < 10; i++) {
                buffer[pos++] = 0;
            }
            pos = buffer.WriteString(pos, "onMetaData", true);
            buffer[pos++] = 0x08;
            pos = buffer.PutInt(pos, 26, 4);

            object o;
            double d;

            pos = buffer.WriteString(pos, "creator");
            pos = buffer.WriteString(pos, "tsorgy.cnblogs.com", true);

            pos = buffer.WriteString(pos, "metadatacreator");
            pos = buffer.WriteString(pos, "Metadata creator - by Tsanie", true);

            pos = buffer.WriteString(pos, "hasKeyframes");
            pos = buffer.WriteByte(pos, 1);
            pos = buffer.WriteString(pos, "hasVideo");
            pos = buffer.WriteByte(pos, 1);
            pos = buffer.WriteString(pos, "hasAudio");
            pos = buffer.WriteByte(pos, 1);
            pos = buffer.WriteString(pos, "hasMetadata");
            pos = buffer.WriteByte(pos, 1);
            pos = buffer.WriteString(pos, "canSeekToEnd");
            pos = buffer.WriteByte(pos, 0);

            pos = buffer.WriteString(pos, "duration");
            pos = buffer.WriteDouble(pos, duration);
            pos = buffer.WriteString(pos, "datasize");
            pos = buffer.WriteDouble(pos, datasize);
            pos = buffer.WriteString(pos, "videosize");
            pos = buffer.WriteDouble(pos, videosize);
            pos = buffer.WriteString(pos, "videocodecid");
            pos = buffer.WriteDouble(pos, videocodec);

            pos = buffer.WriteString(pos, "width");
            d = 512.0;
            if (meta.TryGet("width", out o))
                d = (double)o;
            pos = buffer.WriteDouble(pos, d);

            pos = buffer.WriteString(pos, "height");
            d = 384.0;
            if (meta.TryGet("height", out o))
                d = (double)o;
            pos = buffer.WriteDouble(pos, d);

            pos = buffer.WriteString(pos, "framerate");
            d = framerate > 0 ? framerate : (framecount / duration);
            pos = buffer.WriteDouble(pos, d);

            pos = buffer.WriteString(pos, "videodatarate");
            pos = buffer.WriteDouble(pos, videosize / 125.0 / duration);

            pos = buffer.WriteString(pos, "audiosize");
            pos = buffer.WriteDouble(pos, audiosize);
            pos = buffer.WriteString(pos, "audiocodecid");
            pos = buffer.WriteDouble(pos, audiocodec);
            pos = buffer.WriteString(pos, "audiosamplerate");
            d = 44100;
            if (meta.TryGet("audiosamplerate", out o))
                d = (double)o;
            pos = buffer.WriteDouble(pos, d);
            pos = buffer.WriteString(pos, "audiosamplesize");
            d = 16;
            if (meta.TryGet("audiosamplesize", out o))
                d = (double)o;
            pos = buffer.WriteDouble(pos, d);
            pos = buffer.WriteString(pos, "stereo");
            byte stereo = 1;
            if (meta.TryGet("stereo", out o))
                stereo = (byte)o;
            pos = buffer.WriteByte(pos, stereo);
            pos = buffer.WriteString(pos, "audiodatarate");
            pos = buffer.WriteDouble(pos, audiosize / 125.0 / duration);

            pos = buffer.WriteString(pos, "filesize");
            int filesize_pos = pos;
            pos += 9;

            pos = buffer.WriteString(pos, "lasttimestamp");
            pos = buffer.WriteDouble(pos, lasttimestamp);
            pos = buffer.WriteString(pos, "lastkeyframetimestamp");
            pos = buffer.WriteDouble(pos, lastkeyframetimestamp);
            pos = buffer.WriteString(pos, "lastkeyframelocation");
            pos = buffer.WriteDouble(pos, lastkeyframelocation);

            #endregion

            pos = buffer.WriteString(pos, "keyframes");
            buffer[pos++] = 3; // object
            pos = buffer.WriteString(pos, "filepositions");
            int file_positions = pos;
            pos = buffer.WriteArray(pos, filepositions);
            pos = buffer.WriteString(pos, "times");
            pos = buffer.WriteArray(pos, times);
            buffer[pos++] = 0;
            buffer[pos++] = 0;
            buffer[pos++] = 9; // 结束符

            // script tag 长度
            buffer.PutInt(1, pos - 11, 3); // script 帧的 datasize
            pos = buffer.PutInt(pos, pos, 4);
            buffer.WriteDouble(filesize_pos, datasize + pos + bhead.Length); // filesize
            buffer.WriteArray(file_positions, filepositions, pos + bhead.Length + (reserve ? 27 : 0));

            dest.Write(bhead, 0, bhead.Length);
            dest.Write(buffer, 0, pos);
        }

    }
}
